package com.coderly.onlinegallerysharingplatform.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.coderly.onlinegallerysharingplatform.common.constant.RedisKeyConstants;
import com.coderly.onlinegallerysharingplatform.common.convention.errorcode.ErrorCode;
import com.coderly.onlinegallerysharingplatform.common.convention.exception.BizException;
import com.coderly.onlinegallerysharingplatform.common.response.Response;
import com.coderly.onlinegallerysharingplatform.common.sms.AliyunSmsHelper;
import com.coderly.onlinegallerysharingplatform.enums.LoginTypeEnum;
import com.coderly.onlinegallerysharingplatform.enums.PictureReviewStatusEnum;
import com.coderly.onlinegallerysharingplatform.enums.UserRoleEnum;
import com.coderly.onlinegallerysharingplatform.model.dao.entity.FansDO;
import com.coderly.onlinegallerysharingplatform.model.dao.entity.FollowingDO;
import com.coderly.onlinegallerysharingplatform.model.dao.entity.PictureDO;
import com.coderly.onlinegallerysharingplatform.model.dao.entity.UserDO;
import com.coderly.onlinegallerysharingplatform.model.dao.mapper.FansMapper;
import com.coderly.onlinegallerysharingplatform.model.dao.mapper.FollowingMapper;
import com.coderly.onlinegallerysharingplatform.model.dao.mapper.PictureMapper;
import com.coderly.onlinegallerysharingplatform.model.dao.mapper.UserMapper;
import com.coderly.onlinegallerysharingplatform.model.dto.req.user.ChangePhoneReqDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.req.user.GetUserReqDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.req.user.SendVerificationCodeReqDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.req.user.UserLoginAndRegisterReqDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.resp.pic.PictureUploadCountDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.resp.user.GetUserDateCountInfoRespDTO;
import com.coderly.onlinegallerysharingplatform.model.dto.resp.user.UserRespDTO;
import com.coderly.onlinegallerysharingplatform.service.UserService;
import com.coderly.onlinegallerysharingplatform.toolkit.ExceptionTrowUtils;
import com.coderly.onlinegallerysharingplatform.toolkit.StpKit;
import com.coderly.onlinegallerysharingplatform.toolkit.UserUtil;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.coderly.onlinegallerysharingplatform.toolkit.RedisUtils;

/**
 * @author 26788
 * @description 针对表【t_user(用户表)】的数据库操作Service实现
 * @createDate 2025-03-03 22:32:51
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO>
        implements UserService {

    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisUtils redisUtils;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private final AliyunSmsHelper aliyunSmsHelper;
    @Resource
    private PictureMapper pictureMapper;
    @Resource
    private FollowingMapper followingMapper;
    @Resource
    private FansMapper fansMapper;


    /**
     * 用户登录/注册
     *
     * @param requestParam 请求参数
     * @return 脱敏后信息
     */
    public String loginAndRegister(UserLoginAndRegisterReqDTO requestParam) {
        String phone = requestParam.getPhone();
        String password = requestParam.getPassword();
        LoginTypeEnum loginTypeEnum = LoginTypeEnum.valueOf(requestParam.getLoginType());
        UserDO userDO = null;

        // 登录类型错误
        if (Objects.isNull(loginTypeEnum)) {
            throw new BizException(ErrorCode.LOGIN_TYPE_ERROR);
        }

        // 提前查询用户信息
        userDO = this.getUserByPhone(phone);

        // 判断登录类型
        switch (loginTypeEnum) {
            case VERIFICATION_CODE: // 验证码登录
                handleVerificationCodeLogin(requestParam, phone, userDO);
                // 重新获取用户信息，确保数据是最新的
                userDO = this.getUserByPhone(phone);
                break;
            case PASSWORD: // 密码登录
                handlePasswordLogin(password, userDO);
                break;
            default:
                throw new BizException(ErrorCode.LOGIN_TYPE_ERROR);
        }

        // 确保用户信息不为空
        if (Objects.isNull(userDO)) {
            throw new BizException(ErrorCode.SYSTEM_ERROR, "用户信息获取失败");
        }

        // 保存用户登陆态
        StpKit.DEFAULT.login(userDO.getId());
        String defaultLoginToken = StpKit.DEFAULT.getTokenInfo().tokenValue;
        StpKit.SPACE.login(userDO.getId());
        String spaceLoginToken = StpKit.SPACE.getTokenInfo().tokenValue;

        return defaultLoginToken + " " + spaceLoginToken;
    }

    /**
     * 处理验证码登录
     *
     * @param requestParam
     * @param phone
     * @param userDO
     */
    private void handleVerificationCodeLogin(UserLoginAndRegisterReqDTO requestParam, String phone, UserDO userDO) {
        String verificationCode = requestParam.getCode();
        // 校验入参验证码是否为空
        if (StringUtils.isBlank(phone) || StringUtils.isBlank(verificationCode)) {
            throw new BizException(ErrorCode.PARAMS_ERROR, "参数不能为空");
        }

        // 构建验证码 Redis Key
        String key = RedisKeyConstants.buildVerificationCodeKey(phone);

        try {
            // 从Redis获取验证码
            String sentCode = (String) redisUtils.get(key);

            // 如果Redis中没有验证码，可能是因为超时或已过期
            if (sentCode == null) {
                throw new BizException(ErrorCode.VERIFICATION_CODE_ERROR, "验证码已过期，请重新获取");
            }

            // 验证码比对
            if (!StringUtils.equals(verificationCode, sentCode)) {
                throw new BizException(ErrorCode.VERIFICATION_CODE_ERROR, "验证码错误");
            }

            log.info("验证码验证成功，手机号: {}", phone);

            // 验证成功后删除Redis中的验证码，防止重复使用
            redisUtils.delete(key);

        } catch (BizException be) {
            // 业务异常直接抛出
            throw be;
        }

        log.info("==> 用户是否注册, phone: {}, userDO: {}", phone, JSONUtil.toJsonStr(userDO));
        // 判断用户是否注册
        if (Objects.isNull(userDO)) {
            // 若此用户还没有注册，系统自动注册该用户
            Long userId = registerUser(phone);
            if (userId == null) {
                throw new BizException(ErrorCode.SYSTEM_ERROR, "用户注册失败");
            }
        }
    }

    /**
     * 处理密码登录
     *
     * @param password 用户传来的密码
     * @param userDO   数据库中的对象
     */
    private void handlePasswordLogin(String password, UserDO userDO) {
        // 校验密码是否为空
        if (StringUtils.isBlank(password)) {
            throw new BizException(ErrorCode.PARAMS_ERROR, "密码不能为空");
        }
        // 判断该手机号是否已经注册
        if (Objects.isNull(userDO)) {
            throw new BizException(ErrorCode.USER_NOT_FOUND);
        }
        // 判断用户传来的密码与数据库中密码是否一致
        if (!BCrypt.checkpw(password, userDO.getPassword())) {
            throw new BizException(ErrorCode.PHONE_OR_PASSWORD_ERROR);
        }
    }

    /**
     * 系统自动注册用户
     *
     * @param phone
     * @return
     */
    private Long registerUser(String phone) {
        // 获取系统自增账号
        Long userAccount;
        try {
            userAccount = redisUtils.increment(RedisKeyConstants.USER_ACCOUNT_GENERATOR);
        } catch (Exception e) {
            userAccount = System.currentTimeMillis(); // 防止Redis操作失败
            log.warn("Redis自增账号获取失败，使用时间戳作为替代: {}", userAccount);
        }

        UserDO userDO = UserDO.builder()
                .account(String.valueOf(userAccount))
                .phone(phone)
                .password(BCrypt.hashpw("admin123", BCrypt.gensalt(10)))
                .nickname("图库用户" + userAccount)
                .role(UserRoleEnum.USER.getValue()) // 直接在这里设置角色
                .build();

        // 添加入库
        try {
            userMapper.insert(userDO);
            log.info("用户注册成功: {}", JSONUtil.toJsonStr(userDO));
            // 确保返回的用户对象包含数据库生成的ID
            return userDO.getId();
        } catch (Exception e) {
            log.error("用户注册失败", e);
            throw new BizException(ErrorCode.SYSTEM_ERROR, "用户注册失败");
        }
    }


    /**
     * 发送短信验证码
     *
     * @param sendVerificationCodeReqDTO
     * @return
     */
    @Override
    public void send(SendVerificationCodeReqDTO sendVerificationCodeReqDTO) {
        // 手机号
        String phone = sendVerificationCodeReqDTO.getPhone();

        // 构建验证码 redis key
        String key = RedisKeyConstants.buildVerificationCodeKey(phone);

        // 生成 6 位随机数字验证码
        String verificationCode = RandomUtil.randomNumbers(6);

        log.info("==> 准备发送验证码，手机号: {}, 验证码：【{}】", phone, verificationCode);

        // 检查是否已发送验证码
        try {
            boolean isSent = redisUtils.hasKey(key);
            if (isSent) {
                // 若之前发送的验证码未过期，则提示发送频繁
                log.warn("验证码发送频繁，手机号: {}", phone);
                throw new BizException(ErrorCode.VERIFICATION_CODE_SEND_FREQUENTLY);
            }
        } catch (BizException be) {
            throw be;
        } catch (Exception e) {
            // 如果检查失败，我们假设没有验证码并继续
            log.warn("Redis检查验证码是否存在失败，假设没有验证码并继续，手机号: {}", phone, e);
        }

        // 发送短信
        try {
            // 调用第三方短信发送服务
            threadPoolTaskExecutor.submit(() -> {
                String signName = "阿里云短信测试";
                String templateCode = "SMS_154950909";
                String templateParam = String.format("{\"code\":\"%s\"}", verificationCode);
                aliyunSmsHelper.sendMessage(signName, templateCode, phone, templateParam);
                log.info("==> 短信发送成功，手机号: {}", phone);
            });
        } catch (Exception e) {
            log.error("短信发送失败", e);
            throw new BizException(ErrorCode.SYSTEM_ERROR, "短信发送失败，请稍后重试");
        }

        // 存储验证码到Redis
        try {
            redisTemplate.opsForValue().set(key, verificationCode, 60, TimeUnit.SECONDS);
            redisUtils.set(key, verificationCode, 60, TimeUnit.SECONDS);
            log.info("==> Redis存储验证码成功，手机号: {}", phone);
        } catch (Exception e) {
            // 不抛出异常，因为短信已经发送，避免影响用户体验
            log.error("Redis存储验证码失败，但短信已发送。请检查Redis连接！手机号: {}", phone, e);
        }
    }

    /**
     * 获取登录用户
     *
     * @param
     * @return
     */
    @Override
    public UserDO getLoginUser() {
        Long userId = StpUtil.getLoginIdAsLong();
        // 查询用户信息
        UserDO currentUser = userMapper.selectById(userId);
        // 防止用户信息意外丢失
        if (currentUser == null) {
            throw new BizException(ErrorCode.NOT_LOGIN_ERROR);
        }
        // 手机号脱敏返回
        currentUser.setPhone(DesensitizedUtil.mobilePhone(currentUser.getPhone()));
        return currentUser;
    }

    /**
     * 退出登录
     *
     * @param
     * @return
     */
    @Override
    public Boolean logout() {
        // 移除登录态
        StpUtil.logout();
        return true;
    }

    /**
     * 根据手机号查询用户
     *
     * @param phone
     * @return
     */
    private UserDO getUserByPhone(String phone) {
        LambdaQueryWrapper<UserDO> userDOLambdaQueryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getPhone, phone);

        return userMapper.selectOne(userDOLambdaQueryWrapper);
    }

    @Override
    public UserRespDTO getUser(UserDO userDO) {
        if (userDO == null) {
            return null;
        }
        UserRespDTO userRespDTO = new UserRespDTO();
        BeanUtils.copyProperties(userDO, userRespDTO);
        // 手机号脱敏返回
        userRespDTO.setPhone(DesensitizedUtil.mobilePhone(userRespDTO.getPhone()));
        return userRespDTO;
    }

    @Override
    public List<UserRespDTO> getUserList(List<UserDO> userDOList) {
        if (CollUtil.isEmpty(userDOList)) {
            return new ArrayList<>();
        }
        return userDOList.stream()
                .map(this::getUser)
                .collect(Collectors.toList());
    }

    public QueryWrapper<UserDO> getQueryWrapper(GetUserReqDTO requestParam) {
        if (requestParam == null) {
            throw new BizException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = requestParam.getId();
        String account = requestParam.getAccount();
        String nickname = requestParam.getNickname();
        String introduction = requestParam.getIntroduction();
        String role = requestParam.getRole();
        String phone = requestParam.getPhone();
        String sortField = requestParam.getSortField();
        String sortOrder = requestParam.getSortOrder();

        QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(ObjUtil.isNotNull(id), "id", id);
        queryWrapper.eq(StrUtil.isNotBlank(role), "role", role);
        queryWrapper.like(StrUtil.isNotBlank(account), "account", account);
        queryWrapper.like(StrUtil.isNotBlank(nickname), "nickname", nickname);
        queryWrapper.like(StrUtil.isNotBlank(introduction), "introduction", introduction);
        queryWrapper.like(StrUtil.isNotBlank(phone), "phone", phone);
        queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
        return queryWrapper;
    }

    /**
     * 获取指定用户个人主页数据详情
     *
     * @param userId
     * @return
     */
    @Override
    public Response<GetUserDateCountInfoRespDTO> getUserDateCountInfo(long userId) {
        // 获取用户图片发布总数
        LambdaQueryWrapper<PictureDO> eq1 = Wrappers.lambdaQuery(PictureDO.class)
                .isNull(PictureDO::getSpaceId)
                .eq(PictureDO::getReviewStatus, PictureReviewStatusEnum.PASS.getValue())
                .eq(PictureDO::getUserId, userId);
        Long postsCount = pictureMapper.selectCount(eq1);
        LambdaQueryWrapper<PictureDO> eq2 = Wrappers.lambdaQuery(PictureDO.class)
                .eq(PictureDO::getUserId, userId);
        List<PictureDO> pictureDOS = pictureMapper.selectList(eq2);
        // 获取用户图片获赞总数
        Long likesCount = pictureDOS.stream()
                .mapToLong(PictureDO::getLikeCount)
                .sum();
        // 获取用户关注用户总数
        LambdaQueryWrapper<FollowingDO> eq3 = Wrappers.lambdaQuery(FollowingDO.class)
                .eq(FollowingDO::getUserId, userId);
        Long FollowsCount = followingMapper.selectCount(eq3);
        // 获取用户粉丝总数
        LambdaQueryWrapper<FansDO> eq4 = Wrappers.lambdaQuery(FansDO.class)
                .eq(FansDO::getUserId, userId);
        Long fansCount = fansMapper.selectCount(eq4);
        GetUserDateCountInfoRespDTO build = GetUserDateCountInfoRespDTO.builder()
                .fansCount(fansCount)
                .likesCount(likesCount)
                .postsCount(postsCount)
                .followsCount(FollowsCount)
                .build();
        return Response.success(build);
    }

    @Override
    public Map<LocalDate, Long> getUserPictureUploadHeatMap() {
        // 获取当前登录用户
        Long loginUserId = UserUtil.getLoginUserId();
        // 获取当前时间
        LocalDate currentDate = LocalDate.now();
        //  倒退一年的时间
        LocalDate startDate = currentDate.minusYears(1);
        // 查找过去一年到今天之间，每日发布图片数目
        List<PictureUploadCountDTO> pictureUploadCountDTOS = pictureMapper.selectDatePictureUploadCount(loginUserId, startDate, currentDate.plusDays(1));
        Map<LocalDate, Long> map = null;
        if (!CollUtil.isEmpty(pictureUploadCountDTOS)) {
            // DO 转 Map
            Map<LocalDate, Long> datePictureCountMap = pictureUploadCountDTOS.stream()
                    .collect(Collectors.toMap(PictureUploadCountDTO::getDate, PictureUploadCountDTO::getCount));

            // 有序 Map, 返回的日期图片数需要以升序排列
            map = Maps.newLinkedHashMap();
            // 从上一年的今天循环到今天
            for (; startDate.isBefore(currentDate) || startDate.isEqual(currentDate); startDate = startDate.plusDays(1)) {
                // 以日期作为 key 从 datePictureCountMap 中取图片上传总量
                Long count = datePictureCountMap.get(startDate);
                // 设置到返参 Map
                map.put(startDate, Objects.isNull(count) ? 0 : count);
            }
        }
        return map;
    }

    @Override
    public Boolean changePhone(ChangePhoneReqDTO changePhoneReqDTO) {
        // 获取携带参数
        String phone = changePhoneReqDTO.getPhone();
        String code = changePhoneReqDTO.getCode();
        // 构建验证码 Redis Key
        String key = RedisKeyConstants.buildVerificationCodeKey(phone);
        // 从Redis获取验证码
        String sentCode = (String) redisUtils.get(key);
        // 如果Redis中没有验证码，可能是因为超时或已过期
        if (sentCode == null) {
            throw new BizException(ErrorCode.VERIFICATION_CODE_ERROR, "验证码已过期，请重新获取");
        }
        // 验证码比对
        if (!StringUtils.equals(code, sentCode)) {
            throw new BizException(ErrorCode.VERIFICATION_CODE_ERROR, "验证码错误");
        }
        log.info("验证码验证成功，手机号: {}", phone);
        // 验证成功后删除Redis中的验证码，防止重复使用
        redisUtils.delete(key);
        // 验证手机号是否已被其他人绑定
        UserDO userDO = this.getUserByPhone(phone);
        ExceptionTrowUtils.throwIf(userDO != null, ErrorCode.PHONE_HAS_BOUND);
        // 入库
        Long loginUserId = UserUtil.getLoginUserId();
        boolean update = this.lambdaUpdate()
                .eq(UserDO::getId, loginUserId)
                .set(UserDO::getPhone, phone)
                .update();
        return update;
    }
}



